/**
 * scene package
 * 
 */
package ronin.scene;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;

import ronin.actor2d.*;

/**
 * The Class QuadNode represents a single Node within a QuadTree.
 * The node handles specifics of updating, moving en-mass, and detection
 * of collisions among Actor2D objects. This class takes care of passing
 * collisions to the Actors themselves for further processing. Each node
 * is passed an id, that is searchable in the QuadTree via 
 */
public class QuadNode extends Object {
    // the next unique id to assign a node
    /** The UNIQU e_ id. */
    protected static int UNIQUE_ID = 0;

    // a unique integer to identify this node
    /** The id. */
    protected int id;

    /** the parent that contains this node */
    protected QuadNode parent;


    /** 
     * an array of child nodes for this QuadNode
     * */
    protected QuadNode[] nodes;

    /** true if this node is a leaf node (has no children)*/
    protected boolean leaf;

    /** a linked-list of objects this node contains*/
    protected LinkedList<Moveable> objects;

    /** the bounds of the node. */
    protected Rectangle2D bounds;

    /**
     * Creates a blank quadeNode, basically a null Node
     */
    private QuadNode() {
	id = -1;
	parent = null;
	nodes = null;
	leaf = true;
	objects = null;
	bounds = null;
    }

    // 
    /**
     * Constructs a QuadNode with the given parent, depth, and bounds.
     * 
     * @param p
     *                stands for parent node
     * @param depth
     *                of this node
     * @param r
     *                is the rectangular bounds of the scene.
     */
    public QuadNode(QuadNode p, int depth, Rectangle2D r) {
	parent = p;
	bounds = r;

	id = UNIQUE_ID++;

	// this node is a leaf if the remaining depth is zero
	if (depth == 0) {
	    leaf = true;
	    objects = new LinkedList<Moveable>();
	    nodes = null;

	    QuadTree.addLeaf(this); // / TODO Add comment to begining of this
	    // line when compiling //
	}

	// otherwise, this node contains 4 child nodes
	else {
	    leaf = false;
	    objects = null;

	    nodes = new QuadNode[4];
	    double x = bounds.getX();
	    double y = bounds.getY();
	    double w = bounds.getWidth();
	    double h = bounds.getHeight();

	    // create the children
	    nodes[0] = new QuadNode(this, depth - 1, new Rectangle2D.Double(x,
		    y + h / 2, w / 2, h / 2));
	    nodes[1] = new QuadNode(this, depth - 1, new Rectangle2D.Double(x
		    + w / 2, y + h / 2, w / 2, h / 2));
	    nodes[2] = new QuadNode(this, depth - 1, new Rectangle2D.Double(x
		    + w / 2, y, w / 2, h / 2));
	    nodes[3] = new QuadNode(this, depth - 1, new Rectangle2D.Double(x,
		    y, w / 2, h / 2));
	}
    }

    
    /**
     * Insert attempts to insert the sent object into this node.
     * 
     * @param c
     *                the c
     * @param propagate
     *                the propagate
     */
    public void insert(Moveable c, // the Moveable object to insert
	    boolean propagate // if true, propogate upward if c does not fit
    // within this node's bounds
    ) {
	// try to insert the node
	if (bounds.contains(c.getBounds()) || bounds.intersects(c.getBounds())) {
	    // if this node is a leaf node, insert the object into the list
	    if (isLeaf()) {
		if (objects == null) {
		    objects = new LinkedList<Moveable>();
		}
		if (!objects.contains(c)) {
		    objects.add(c);
		}
	    }

	    // if this node is not a leaf, try to insert downward
	    else {
		for (int i = 0; i < 4; i++) {
		    if (nodes[i] != null) {
			nodes[i].insert(c, false);
		    }
		}
	    }
	}

	// otherwise, insert the object upward if propagation is allowed
	else {
	    if (propagate) {
		if (parent != null) {
		    parent.insert(c, true);
		}
	    }
	}
    }

    // 
    /**
     * Checker to see if this is a leaf node.
     * 
     * @return true if this node is a leaf node.
     */
    public final boolean isLeaf() {
	return leaf;
    }

   
    /**
     * Move all; translates all of the objects this node contains by x, y.
     * 
     * @param x is the amount to move horizontally
     * @param y is the amount to move vertically
     */
    public void moveAll(double x, double y) {
	if (objects == null || objects.isEmpty())
	    return;

	Actor2D a;
	for (int i = 0; i < objects.size(); i++) {
	    a = (Actor2D) objects.get(i);
	    a.moveBy(x, y);
	}
    }

    /**
     * Paint bounds draws each node using the given color.
     * 
     * @param g2d is the Graphics2D context in which we use to do our painting.
     * @param color is simple the color we wish to used to draw our node borders. If color
     * is null, then color defaults to red. 
     */
    public void paintBounds(Graphics2D g2d, Color color) {
	
	if (color == null) color = Color.RED;
	
	g2d.setPaint(color);
	g2d.draw(bounds);
    }

    /**
     * Passes dead objects to the parent node via parent.passTheDead(). Eventually the 
     * dead objects will end up starting node or QuadTree of the tree this node belongs to. 
     * Then by default the dead object will rest in the sceneManager of the QuadTree via the 
     * sceneManager.addToGraveYard().
     * 
     * @param m is the Move-able object that has been pronounced dead. 
     */
    public void passTheDead(Moveable m) {
	if (parent != null) {
	    parent.passTheDead(m);
	}

    }

    /**
     * Update simply goes through each Actor in the node, 
     * updates it, and then checks to see if its dead or has 
     * left the node. If the object is dead, it is passed to 
     * the parent until it reaches the SceneManager's Grave-yard. 
     * If it leaves the node it is then passed to the parent and 
     * auto-magically placed in the correct node(s).
     * 
     * After this check the collision detection check is ran. This is run 
     * after the prev checks and balances because some objects may have died 
     * or left the node during the last update. 
     */
    public void update() {
	if (objects == null || objects.isEmpty()) {
	    return;
	}

	// update the list, then remove objects that are no longer in this node
	Actor2D m;
	
	for (int i = 0; i < objects.size(); i++) {
	    m = (Actor2D) objects.get(i);

	    // If OBJECT dead Nullify it.
	    if (m.getState() == 0) {
		// place it off-screen so that it isn't mistakenly painted
		m.setPos(-1000, -1200); 
		passTheDead((Moveable) objects.remove(i));
		continue;
	    }
	    m.update();

	    // test if the object left the node; if so, insert upward
	    if (!bounds.contains(m.getBounds())
		    && !bounds.intersects(m.getBounds())) // might propagate
	    // upward
	    {
		insert((Moveable) objects.remove(i), true);
	    }
	}

	// get the updated size since some objects may have been removed
	int size = objects.size();

	// test each object for collision against each other object in this node
	for (int i = 0; i < size - 1; i++) {
	    Moveable a = (Moveable) objects.get(i);

	    for (int j = i + 1; j < size; j++) {
		Moveable b = (Moveable) objects.get(j);

		if (a.collidesWith(b)) {
		    a.addCollision(b);
		    b.addCollision(a);
		}
	    }
	}
    }

	public Vector findCollisionsWith(Actor2D actor){
		if(objects.size() == 0) { return null; }
		if(!bounds.contains(actor.getBounds()) || 
			!bounds.intersects(actor.getBounds())){ 
			return null; }
		
		Vector<Actor2D> collisions = new Vector<Actor2D>();
		
		for(Moveable object : objects){
			if(object.collidesWith(actor)){
				collisions.add((Actor2D) object);
			}
		}
		if(collisions.size() == 0){
			return null;
		}
		
		return collisions;
	}
	
	public boolean checkForCollision(Rectangle2D area){
		if(!bounds.contains(area) || !bounds.intersects(area)) return false;
		
		for(Moveable m : objects){
			
			if( ((Actor2D) m).getBounds().contains(area) || 
				((Actor2D) m).getBounds().intersects(area) ){
				return true;
			}
		}
		
		return false;
	}
} // QuadNode
